// This file contains some general utility functions that are not specific to CObject
#pragma once
#ifndef _KH_CORE_OBJECTUTIL_H
#define _KH_CORE_OBJECTUTIL_H
#include <Kharlia/Core/Common.h>
#include <boost/python/object/find_instance.hpp>
#include <hash_map>

namespace kharlia { namespace core { namespace util {
/// Callable python object used to construct instances of native objects
extern KH_CORE_API object GConstructObjectFunc;

/// Name resolver
extern KH_CORE_API object GFindGlobalFunc;

/// Wrapper for object constructor

#define _CO_NUM_ARGS 8
#define _CO_ARGS(z, n, data) A##n a##n

#define _CO(z, n, data) \
    template <BOOST_PP_ENUM_PARAMS(n, class A)> \
    inline object ConstructObject(data cls, BOOST_PP_ENUM(n, _CO_ARGS, ~)) \
    { return GConstructObjectFunc(cls, BOOST_PP_ENUM_PARAMS(n, a)); }

#define _CO_EX(z, n, data) \
    template <class R BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, class A)> \
    inline shared_ptr<R> ConstructObjectEx(data cls BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM(n, _CO_ARGS, ~)) \
    { return extract<shared_ptr<R>>(GConstructObjectFunc(cls BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, a))); }

BOOST_PP_REPEAT_FROM_TO(1, _CO_NUM_ARGS, _CO, object)
BOOST_PP_REPEAT_FROM_TO(1, _CO_NUM_ARGS, _CO, str)
BOOST_PP_REPEAT(_CO_NUM_ARGS, _CO_EX, object)
BOOST_PP_REPEAT(_CO_NUM_ARGS, _CO_EX, str)

#undef _CO
#undef _CO_EX
#undef _CO_ARGS
#undef _CO_NUM_ARGS

/// Name resolver wrapper
inline object FindGlobal(str name) {
    return GFindGlobalFunc(name);
}
inline object FindGlobal(object name) {
    return GFindGlobalFunc(name);
}

/// Resolve name and extract type T
template <class ExtractType>
inline ExtractType FindGlobalEx(str name) {
    return extract<ExtractType>(GFindGlobalFunc(name));
}

/// Returns a python weakref to obj. If obj is a weak ref it is returned as-is.
KH_CORE_API object GetWeakRef(object obj, object callback=object());

/// Returns the Boost.Python.class object for the specified type info or None if
/// one does not exist.
inline object GetTypeObject(py::type_info info) {
    converter::registration const* reg =
        converter::registry::query(info);
    if (reg) {
        return object(handle<>(borrowed(reg->get_class_object())));
    }
    return object();
}

/// Returns true if a shared_ptr is managing a python object
template <class T>
inline bool IsManagingPyObject(shared_ptr<T> const& p) {
    return boost::get_deleter<converter::shared_ptr_deleter>(p) != nullptr;
}

/// Calls nextFunc and sets out to the result. Returns true if this was
/// successful, false if StopIteration was caught.
KH_CORE_API bool Iterate(object const& nextFunc, object& out);

// Returns object.__iter__()
inline object GetIter(object const& iterable) {
    return object(handle<>(PyObject_GetIter(iterable.ptr())));
}

inline bool IterNext(object const& iterator, object& out) {
    handle<> n(allow_null(PyIter_Next(iterator.ptr())));
    if (n.get() == nullptr) {
        if (PyErr_Occurred())
            throw_error_already_set();
        else
            return false;
    }
    out = object(n);
    return true;
}

template <typename T>
inline bool IterNextEx(object const& iterator, T& out) {
    handle<> n(allow_null(PyIter_Next(iterator.ptr())));
    if (n.get() == nullptr) {
        if (PyErr_Occurred())
            throw_error_already_set();
        else
            return false;
    }
    out = extract<T>(n.get());
    return true;
}

// Returns object.__iter__().next
// Use this function for retrieving iterators for use with Iterate().
inline object GetIterNextFunc(object const& object) {
    return object.attr("__iter__")().attr("next");
}

// This macro simplifies Python object iteration
#define PY_FOR(out, iterable) \
    for (object out, __iterator##out = kharlia::core::util::GetIter(iterable); \
         kharlia::core::util::IterNext(__iterator##out, out);)

} // namespace util

using namespace util;

}} //namespace Kharlia::core

#endif